#include "CITLValidatorImpl.h"

#ifdef _WIN32

#include <sstream>
#include <algorithm>
#include <thread>
#include <windows.h>
#include <QLibrary>
#include <QThread>

#include "ITLSSPProc/ITLSSPProc.h"
#include "glog/logging.h"
#include "CommonType.h"

using namespace zl;

typedef int (*_SSPSendCommand)(SSP_COMMAND*, SSP_COMMAND_INFO*);
typedef int (*_OpenSSPComPort)(SSP_COMMAND*);
typedef int (*_CloseSSPComPort)(void);

_SSPSendCommand pfunc_SSPSendCommand = nullptr;
_OpenSSPComPort pfunc_OpenSSPComPort = nullptr;
_CloseSSPComPort pfunc_CloseSSPComPort = nullptr;

/// itl ssp command
struct Commands {
    unsigned char SSP_CMD_RESET = 0x01;
    unsigned char SSP_CMD_SET_CHANNEL_INHIBITS = 0x02;
    unsigned char SSP_CMD_DISPLAY_ON = 0x03;
    unsigned char SSP_CMD_DISPLAY_OFF = 0x04;
    unsigned char SSP_CMD_SETUP_REQUEST = 0x05;
    unsigned char SSP_CMD_HOST_PROTOCOL_VERSION = 0x06;
    unsigned char SSP_CMD_POLL = 0x07;
    unsigned char SSP_CMD_REJECT_BANKNOTE = 0x08;
    unsigned char SSP_CMD_DISABLE = 0x09;
    unsigned char SSP_CMD_ENABLE = 0x0A;
    unsigned char SSP_CMD_GET_SERIAL_NUMBER = 0x0C;
    unsigned char SSP_CMD_UNIT_DATA = 0x0D;
    unsigned char SSP_CMD_CHANNEL_VALUE_REQUEST = 0x0E;
    unsigned char SSP_CMD_CHANNEL_SECURITY_DATA = 0x0F;
    unsigned char SSP_CMD_CHANNEL_RE_TEACH_DATA = 0x10;
    unsigned char SSP_CMD_SYNC = 0x11;
    unsigned char SSP_CMD_LAST_REJECT_CODE = 0x17;
    unsigned char SSP_CMD_HOLD = 0x18;
    unsigned char SSP_CMD_GET_FIRMWARE_VERSION = 0x20;
    unsigned char SSP_CMD_GET_DATASET_VERSION = 0x21;
    unsigned char SSP_CMD_GET_ALL_LEVELS = 0x22;
    unsigned char SSP_CMD_GET_BAR_CODE_READER_CONFIGURATION = 0x23;
    unsigned char SSP_CMD_SET_BAR_CODE_CONFIGURATION = 0x24;
    unsigned char SSP_CMD_GET_BAR_CODE_INHIBIT_STATUS = 0x25;
    unsigned char SSP_CMD_SET_BAR_CODE_INHIBIT_STATUS = 0x26;
    unsigned char SSP_CMD_GET_BAR_CODE_DATA = 0x27;
    unsigned char SSP_CMD_SET_REFILL_MODE = 0x30;
    unsigned char SSP_CMD_PAYOUT_AMOUNT = 0x33;
    unsigned char SSP_CMD_SET_DENOMINATION_LEVEL = 0x34;
    unsigned char SSP_CMD_GET_DENOMINATION_LEVEL = 0x35;
    unsigned char SSP_CMD_COMMUNICATION_PASS_THROUGH = 0x37;
    unsigned char SSP_CMD_HALT_PAYOUT = 0x38;
    unsigned char SSP_CMD_SET_DENOMINATION_ROUTE = 0x3B;
    unsigned char SSP_CMD_GET_DENOMINATION_ROUTE = 0x3C;
    unsigned char SSP_CMD_FLOAT_AMOUNT = 0x3D;
    unsigned char SSP_CMD_GET_MINIMUM_PAYOUT = 0x3E;
    unsigned char SSP_CMD_EMPTY_ALL = 0x3F;
    unsigned char SSP_CMD_SET_COIN_MECH_INHIBITS = 0x40;
    unsigned char SSP_CMD_GET_NOTE_POSITIONS = 0x41;
    unsigned char SSP_CMD_PAYOUT_NOTE = 0x42;
    unsigned char SSP_CMD_STACK_NOTE = 0x43;
    unsigned char SSP_CMD_FLOAT_BY_DENOMINATION = 0x44;
    unsigned char SSP_CMD_SET_VALUE_REPORTING_TYPE = 0x45;
    unsigned char SSP_CMD_PAYOUT_BY_DENOMINATION = 0x46;
    unsigned char SSP_CMD_SET_COIN_MECH_GLOBAL_INHIBIT = 0x49;
    unsigned char SSP_CMD_SET_GENERATOR = 0x4A;
    unsigned char SSP_CMD_SET_MODULUS = 0x4B;
    unsigned char SSP_CMD_REQUEST_KEY_EXCHANGE = 0x4C;
    unsigned char SSP_CMD_SET_BAUD_RATE = 0x4D;
    unsigned char SSP_CMD_GET_BUILD_REVISION = 0x4F;
    unsigned char SSP_CMD_SET_HOPPER_OPTIONS = 0x50;
    unsigned char SSP_CMD_GET_HOPPER_OPTIONS = 0x51;
    unsigned char SSP_CMD_SMART_EMPTY = 0x52;
    unsigned char SSP_CMD_CASHBOX_PAYOUT_OPERATION_DATA = 0x53;
    unsigned char SSP_CMD_CONFIGURE_BEZEL = 0x54;
    unsigned char SSP_CMD_POLL_WITH_ACK = 0x56;
    unsigned char SSP_CMD_EVENT_ACK = 0x57;
    unsigned char SSP_CMD_GET_COUNTERS = 0x58;
    unsigned char SSP_CMD_RESET_COUNTERS = 0x59;
    unsigned char SSP_CMD_COIN_MECH_OPTIONS = 0x5A;
    unsigned char SSP_CMD_DISABLE_PAYOUT_DEVICE = 0x5B;
    unsigned char SSP_CMD_ENABLE_PAYOUT_DEVICE = 0x5C;
    unsigned char SSP_CMD_SET_FIXED_ENCRYPTION_KEY = 0x60;
    unsigned char SSP_CMD_RESET_FIXED_ENCRYPTION_KEY = 0x61;
    unsigned char SSP_CMD_REQUEST_TEBS_BARCODE = 0x65;
    unsigned char SSP_CMD_REQUEST_TEBS_LOG = 0x66;
    unsigned char SSP_CMD_TEBS_UNLOCK_ENABLE = 0x67;
    unsigned char SSP_CMD_TEBS_UNLOCK_DISABLE = 0x68;

    unsigned char SSP_POLL_TEBS_CASHBOX_OUT_OF_SERVICE = 0x90;
    unsigned char SSP_POLL_TEBS_CASHBOX_TAMPER = 0x91;
    unsigned char SSP_POLL_TEBS_CASHBOX_IN_SERVICE = 0x92;
    unsigned char SSP_POLL_TEBS_CASHBOX_UNLOCK_ENABLED = 0x93;
    unsigned char SSP_POLL_JAM_RECOVERY = 0xB0;
    unsigned char SSP_POLL_ERROR_DURING_PAYOUT = 0xB1;
    unsigned char SSP_POLL_SMART_EMPTYING = 0xB3;
    unsigned char SSP_POLL_SMART_EMPTIED = 0xB4;
    unsigned char SSP_POLL_CHANNEL_DISABLE = 0xB5;
    unsigned char SSP_POLL_INITIALISING = 0xB6;
    unsigned char SSP_POLL_COIN_MECH_ERROR = 0xB7;
    unsigned char SSP_POLL_EMPTYING = 0xC2;
    unsigned char SSP_POLL_EMPTIED = 0xC3;
    unsigned char SSP_POLL_COIN_MECH_JAMMED = 0xC4;
    unsigned char SSP_POLL_COIN_MECH_RETURN_PRESSED = 0xC5;
    unsigned char SSP_POLL_PAYOUT_OUT_OF_SERVICE = 0xC6;
    unsigned char SSP_POLL_NOTE_FLOAT_REMOVED = 0xC7;
    unsigned char SSP_POLL_NOTE_FLOAT_ATTACHED = 0xC8;
    unsigned char SSP_POLL_NOTE_TRANSFERED_TO_STACKER = 0xC9;
    unsigned char SSP_POLL_NOTE_PAID_INTO_STACKER_AT_POWER_UP = 0xCA;
    unsigned char SSP_POLL_NOTE_PAID_INTO_STORE_AT_POWER_UP = 0xCB;
    unsigned char SSP_POLL_NOTE_STACKING = 0xCC;
    unsigned char SSP_POLL_NOTE_DISPENSED_AT_POWER_UP = 0xCD;
    unsigned char SSP_POLL_NOTE_HELD_IN_BEZEL = 0xCE;
    unsigned char SSP_POLL_BAR_CODE_TICKET_ACKNOWLEDGE = 0xD1;
    unsigned char SSP_POLL_DISPENSED = 0xD2;
    unsigned char SSP_POLL_JAMMED = 0xD5;
    unsigned char SSP_POLL_HALTED = 0xD6;
    unsigned char SSP_POLL_FLOATING = 0xD7;
    unsigned char SSP_POLL_FLOATED = 0xD8;
    unsigned char SSP_POLL_TIME_OUT = 0xD9;
    unsigned char SSP_POLL_DISPENSING = 0xDA;
    unsigned char SSP_POLL_NOTE_STORED_IN_PAYOUT = 0xDB;
    unsigned char SSP_POLL_INCOMPLETE_PAYOUT = 0xDC;
    unsigned char SSP_POLL_INCOMPLETE_FLOAT = 0xDD;
    unsigned char SSP_POLL_CASHBOX_PAID = 0xDE;
    unsigned char SSP_POLL_COIN_CREDIT = 0xDF;
    unsigned char SSP_POLL_NOTE_PATH_OPEN = 0xE0;
    unsigned char SSP_POLL_NOTE_CLEARED_FROM_FRONT = 0xE1;
    unsigned char SSP_POLL_NOTE_CLEARED_TO_CASHBOX = 0xE2;
    unsigned char SSP_POLL_CASHBOX_REMOVED = 0xE3;
    unsigned char SSP_POLL_CASHBOX_REPLACED = 0xE4;
    unsigned char SSP_POLL_BAR_CODE_TICKET_VALIDATED = 0xE5;
    unsigned char SSP_POLL_FRAUD_ATTEMPT = 0xE6;
    unsigned char SSP_POLL_STACKER_FULL = 0xE7;
    unsigned char SSP_POLL_DISABLED = 0xE8;
    unsigned char SSP_POLL_UNSAFE_NOTE_JAM = 0xE9;
    unsigned char SSP_POLL_SAFE_NOTE_JAM = 0xEA;
    unsigned char SSP_POLL_NOTE_STACKED = 0xEB;
    unsigned char SSP_POLL_NOTE_REJECTED = 0xEC;
    unsigned char SSP_POLL_NOTE_REJECTING = 0xED;
    unsigned char SSP_POLL_CREDIT_NOTE = 0xEE;
    unsigned char SSP_POLL_READ_NOTE = 0xEF;
    unsigned char SSP_POLL_SLAVE_RESET = 0xF1;

    unsigned char SSP_RESPONSE_OK = 0xF0;
    unsigned char SSP_RESPONSE_COMMAND_NOT_KNOWN = 0xF2;
    unsigned char SSP_RESPONSE_WRONG_NO_PARAMETERS = 0xF3;
    unsigned char SSP_RESPONSE_PARAMETER_OUT_OF_RANGE = 0xF4;
    unsigned char SSP_RESPONSE_COMMAND_CANNOT_BE_PROCESSED = 0xF5;
    unsigned char SSP_RESPONSE_SOFTWARE_ERROR = 0xF6;
    unsigned char SSP_RESPONSE_FAIL = 0xF8;
    unsigned char SSP_RESPONSE_KEY_NOT_SET = 0xFA;
};
constexpr Commands CCommands = Commands();

struct ChannelData
{
    int Value{ 0 };
    unsigned char Channel{ 0x00 };
    char Currency[3]{ 0x00 };
    int Level{ 0 };
    bool Recycling{ false };
};

struct CITLValidatorImpl::SSPImpl
{
    SSP_COMMAND cmd;
    SSP_COMMAND_INFO info;

    char unit_type;
    int stacked_notes;
    int number_of_channels;
    int value_multiplier;
    int hold_number;
    int hold_count;
    bool note_held;
    int protocol_version;
    std::vector<ChannelData> unit_data_list;

    SSPImpl() :
        cmd(SSP_COMMAND()),
        info(SSP_COMMAND_INFO()),
        unit_type(0xff),
        stacked_notes(0),
        number_of_channels(0),
        value_multiplier(1),
        hold_number(0),
        hold_count(0),
        note_held(false),
        protocol_version(0) {}
};

CITLValidatorImpl::CITLValidatorImpl() :
    impl_(new SSPImpl),
    obs_(nullptr),
    polling_flag_(false),
    polling_thread_flag_(false),
    init_flag_(false),
    status_(0)
{

}

CITLValidatorImpl::~CITLValidatorImpl()
{
    delete impl_;
    impl_ = nullptr;
}

int32_t CITLValidatorImpl::Initialize(const std::string& com, int32_t baud, IValidatorObserver *obs)
{
    std::unique_lock<std::mutex> locker(mutex_);
    std::unique_lock<std::mutex> locker2(mutex_api_);

    LOG(INFO) << "init validator: " << com << "@" << baud;
    obs_ = obs;
    // load library
    QLibrary* lib = new QLibrary("./ITLSSPProc.dll");
    if (!lib->load())
    {
        LOG(ERROR) << "load ITLSSPProc.dll failed";
        return zl::EResult_Failed;
    }

    // export symbols
    pfunc_OpenSSPComPort = (_OpenSSPComPort)lib->resolve("OpenSSPComPort");
    pfunc_CloseSSPComPort = (_CloseSSPComPort)lib->resolve("CloseSSPComPort");
    pfunc_SSPSendCommand = (_SSPSendCommand)lib->resolve("SSPSendCommand");
    if (pfunc_OpenSSPComPort == nullptr || pfunc_CloseSSPComPort == nullptr
        || pfunc_SSPSendCommand == nullptr)
    {
        LOG(ERROR) << "export symbols failed";
        return zl::EResult_Failed;
    }

    // set com configuration
    QString tmp = QString(com.c_str()).remove(0, 3);
    impl_->cmd.PortNumber = tmp.toInt();
    impl_->cmd.BaudRate = baud;
    impl_->cmd.Timeout = 500;
    impl_->cmd.SSPAddress = 0;
    impl_->cmd.RetryLevel = 0x01;
    impl_->cmd.EncryptionStatus = false;

    // open ssp com port
    auto ret = pfunc_OpenSSPComPort(&impl_->cmd);
    impl_->cmd.EncryptionStatus = false;
    if (!ret)
    {
        LOG(ERROR) << "open validator failed";
        return zl::EResult_Failed;
    }

    // start polling work thread
    polling_thread_flag_ = true;
    polling_thread_ = new std::thread(&CITLValidatorImpl::poll_loop_worker, this);
    polling_thread_->detach();

    init_flag_ = true;
    LOG(INFO) << "open validator success";
    return zl::EResult_Success;
}

int32_t CITLValidatorImpl::Uninitialize()
{
    std::unique_lock<std::mutex> locker(mutex_);
    std::unique_lock<std::mutex> locker2(mutex_api_);

    LOG(INFO) << "uninitialize validator";
    if (!init_flag_)
    {
        return zl::EResult_Success;
    }

    pfunc_CloseSSPComPort();

    // stop polling work thread
    if (polling_thread_ != nullptr)
    {
        polling_thread_flag_ = false;
        if (polling_thread_->joinable())
        {
            polling_thread_->join();
        }
    }
    delete polling_thread_;
    polling_thread_ = nullptr;

    delete impl_;
    init_flag_ = false;
    return zl::EResult_Success;
}

int32_t CITLValidatorImpl::Status()
{
    return status_;
}

int32_t CITLValidatorImpl::Reset()
{
    std::unique_lock<std::mutex> locker(mutex_api_);
    if (!init_flag_)
    {
        return zl::EResult_Failed;
    }

    int32_t ret(0);
    // send reset command
    ret = ssp_cmd_reset();
    if (ret != zl::EResult_Success)
    {
        LOG(ERROR) << "execute command reset failed ";
        return zl::EResult_Failed;
    }

    // close com port
    if (pfunc_CloseSSPComPort == nullptr)
    {
        LOG(ERROR) << "ssp symbol CloseSSPComPort is nullptr";
        return zl::EResult_Failed;
    }
    pfunc_CloseSSPComPort();

    // reopen com port
    if (pfunc_OpenSSPComPort == nullptr)
    {
        LOG(ERROR) << "ssp symbol OpenSSPComPort is nullptr";
        return zl::EResult_Failed;
    }
    pfunc_OpenSSPComPort(&impl_->cmd);

    // sync
    ret = ssp_cmd_sync();

    // sync
    ret = ssp_cmd_sync();

    // sync
    ret = ssp_cmd_sync();
    if (ret != zl::EResult_Success)
    {
        LOG(ERROR) << "reset validator failed, sync failed";
        return zl::EResult_Failed;
    }

    return zl::EResult_Success;
}

int32_t CITLValidatorImpl::Setup()
{
    std::unique_lock<std::mutex> locker(mutex_api_);
    if (!init_flag_)
    {
        return zl::EResult_Failed;
    }

    LOG(INFO) << "setup validator";
    int32_t ret(0);
    // send ssp command sync
    ret = ssp_cmd_sync();
    if (ret != zl::EResult_Success)
    {
        LOG(ERROR) << "setup validator sync failed: " << ret;
        return zl::EResult_Failed;
    }

    // send ssp command set version
    ret = ssp_cmd_set_version();
    if (ret != zl::EResult_Success)
    {
        LOG(ERROR) << "setup validator failed, set protocol version failed: " << ret;
        return zl::EResult_Failed;
    }

    // send ssp command setup
    ret = ssp_cmd_setup_request();
    if (ret != zl::EResult_Success)
    {
        LOG(ERROR) << "setup validator setup_request failed: " << ret;
        return zl::EResult_Failed;
    }

    // send ssp command set_inhibits
    ret = ssp_cmd_set_inhibits();
    if (ret != zl::EResult_Success)
    {
        LOG(ERROR) << "setup validator set_inhibits failed: " << ret;
        return zl::EResult_Failed;
    }

    LOG(INFO) << "setup validator success...";
    return zl::EResult_Success;
}

int32_t CITLValidatorImpl::Enable()
{
    std::unique_lock<std::mutex> locker(mutex_api_);
    if (!init_flag_)
    {
        return zl::EResult_Failed;
    }
    LOG(INFO) << "enable validator";

    /// send ssp command sync
    auto ret = ssp_cmd_sync();
    if (ret != zl::EResult_Success)
    {
        LOG(ERROR) << "enable validator sync failed: " << ret;
        return zl::EResult_Failed;
    }

    /// send ssp command enable
    ret = ssp_cmd_enable();
    if (ret != zl::EResult_Success)
    {
        LOG(ERROR) << "enable validator enable failed: " << ret;
        return zl::EResult_Failed;
    }

    /// send ssp command poll
    ret = ssp_cmd_poll();
    if (ret != zl::EResult_Success)
    {
        LOG(ERROR) << "enable validator poll failed:" << ret;
    }

    status_ = 1;
    polling_flag_ = true;
    /// start a thread to run poll loop
    LOG(INFO) << "enable validator success";
    return zl::EResult_Success;
}

int32_t CITLValidatorImpl::Disable()
{
    std::unique_lock<std::mutex> locker(mutex_api_);
    if (!init_flag_)
    {
        return zl::EResult_Failed;
    }
    if (status_ == 0)
    {
        return zl::EResult_Success;
    }
    LOG(INFO) << "disable validator";

    /// send ssp command poll
    auto ret = ssp_cmd_poll();
    ret = ssp_cmd_disable();
    if (ret != zl::EResult_Success)
    {
        LOG(ERROR) << "disable validator failed: " << ret;
        return zl::EResult_Failed;
    }

    status_ = 0;
    polling_flag_ = false;
    LOG(INFO) << "disable validator success";
    return zl::EResult_Success;
}

int32_t CITLValidatorImpl::ssp_cmd_enable()
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "enable validator";

    impl_->cmd.CommandData[0] = CCommands.SSP_CMD_ENABLE;
    impl_->cmd.CommandDataLength = 1;
    if (!ssp_send_command() || !check_generic_responses())
    {
        LOG(ERROR) << "enable validator failed";
        return zl::EResult_Failed;
    }

    LOG(INFO) << "enable validator success";
    return zl::EResult_Success;
}

int32_t CITLValidatorImpl::ssp_cmd_disable()
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "disable validator";

    impl_->cmd.CommandData[0] = CCommands.SSP_CMD_DISABLE;
    impl_->cmd.CommandDataLength = 1;
    if (!ssp_send_command() || !check_generic_responses())
    {
        LOG(ERROR) << "disable validator failed";
        return zl::EResult_Failed;
    }

    LOG(INFO) << "disable validator success";
    return zl::EResult_Success;
}

int32_t CITLValidatorImpl::ssp_cmd_return_note()
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "return note";

    impl_->cmd.CommandData[0] = CCommands.SSP_CMD_REJECT_BANKNOTE;
    impl_->cmd.CommandDataLength = 1;
    if (!ssp_send_command() || !check_generic_responses())
    {
        LOG(ERROR) << "return note failed";
        return zl::EResult_Failed;
    }

    LOG(INFO) << "return note success";
    return zl::EResult_Success;
}

int32_t CITLValidatorImpl::ssp_cmd_reset()
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "reset validator";

    impl_->cmd.CommandData[0] = CCommands.SSP_CMD_RESET;
    impl_->cmd.CommandDataLength = 1;
    if (!ssp_send_command() || !check_generic_responses())
    {
        LOG(ERROR) << "reset validator failed";
        return zl::EResult_Failed;
    }

    LOG(INFO) << "reset validator success";
    return zl::EResult_Success;
}

int32_t CITLValidatorImpl::ssp_cmd_sync()
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "send validator sync";

    impl_->cmd.CommandData[0] = CCommands.SSP_CMD_SYNC;
    impl_->cmd.CommandDataLength = 1;
    if (!ssp_send_command() || !check_generic_responses())
    {
        LOG(ERROR) << "send validator sync failed";
        return zl::EResult_Failed;
    }

    LOG(INFO) << "send validator sync success";
    return zl::EResult_Success;
}

int32_t CITLValidatorImpl::ssp_cmd_setup_request()
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "validator setup request";

    impl_->cmd.CommandData[0] = CCommands.SSP_CMD_SETUP_REQUEST;
    impl_->cmd.CommandDataLength = 1;
    if (!ssp_send_command())
    {
        LOG(ERROR) << "validator setup request failed, send command failed";
        return zl::EResult_Failed;
    }

    std::ostringstream oss;
    int index = 1;
    oss << "\r\nDevice informations:\r\n";

    /// unit type
    impl_->unit_type = (char)impl_->cmd.ResponseData[index++];
    oss << "Unit Type: ";
    switch (impl_->unit_type)
    {
    case (char)0x00: oss << "Validator"; break;
    case (char)0x03: oss << "SMART Hopper"; break;
    case (char)0x06: oss << "SMART Payout"; break;
    case (char)0x07: oss << "NV11"; break;
    case (char)0x0D: oss << "TEBS"; break;
    default: oss << "Unknown Type"; break;
    }
    oss << "\r\n";

    // firmware
    oss << "Firmware: ";
    oss << (char)(impl_->cmd.ResponseData[index++]);
    oss << (char)(impl_->cmd.ResponseData[index++]);
    oss << ".";
    oss << (char)(impl_->cmd.ResponseData[index++]);
    oss << (char)(impl_->cmd.ResponseData[index++]);
    oss << "\r\n";

    /// country code, legacy code so skip it
    index += 3;

    /// value multiplier, legacy code so skip it
    index += 3;

    /// number of channels
    impl_->number_of_channels = impl_->cmd.ResponseData[index++];
    oss << "Number of Channels: " << impl_->number_of_channels << "\r\n";

    /// channel values, legacy code so skip it
    index += impl_->number_of_channels;

    /// channel security, legacy code so skip it
    index += impl_->number_of_channels;

    /// real value multiplier(big endian)
    impl_->value_multiplier = impl_->cmd.ResponseData[index + 2];
    impl_->value_multiplier += impl_->cmd.ResponseData[index + 1] << 8;
    impl_->value_multiplier += impl_->cmd.ResponseData[index] << 16;
    index += 3;
    oss << "Real Value Multiplier: " << impl_->value_multiplier << "\r\n";

    /// protocol version
    impl_->protocol_version = impl_->cmd.ResponseData[index++];
    oss << "Protocol Version: " << impl_->protocol_version << "\r\n";

    /// add channel data to list then display
    impl_->unit_data_list.clear();
    for (int i = 0; i < impl_->number_of_channels; ++i)
    {
        ChannelData channeldata;
        /// channel number
        channeldata.Channel = (unsigned char)(i + 1);
        /// channel value
        int32_t v1 = (int32_t)impl_->cmd.ResponseData[index + (impl_->number_of_channels * 3) + (i * 4)];
        channeldata.Value = v1 * impl_->value_multiplier;
        /// channel Currency
        channeldata.Currency[0] = impl_->cmd.ResponseData[index + (i * 3)];
        channeldata.Currency[1] = impl_->cmd.ResponseData[(index + 1) + (i * 3)];
        channeldata.Currency[2] = impl_->cmd.ResponseData[(index + 2) + (i * 3)];
        /// channel level
        channeldata.Level = 0;
        /// channel recycling
        channeldata.Recycling = false;
        impl_->unit_data_list.push_back(channeldata);

        /// display;
        oss << "Channel " << (int)channeldata.Channel << ": "
            << channeldata.Value / impl_->value_multiplier
            << " " << channeldata.Currency << "\r\n";
    }
    /// sort channel data list
    std::stable_sort(impl_->unit_data_list.begin(), impl_->unit_data_list.end(),
        [](const ChannelData& d1, const ChannelData& d2) -> bool { return d1.Value < d2.Value; });
    LOG(INFO) << oss.str();
    return zl::EResult_Success;
}

int32_t CITLValidatorImpl::ssp_cmd_set_inhibits()
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "set validator inhibits";

    impl_->cmd.CommandData[0] = CCommands.SSP_CMD_SET_CHANNEL_INHIBITS;
    impl_->cmd.CommandData[1] = 0xFF;
    impl_->cmd.CommandData[2] = 0xFF;
    impl_->cmd.CommandDataLength = 3;
    if (!ssp_send_command() || !check_generic_responses())
    {
        LOG(ERROR) << "set validator inhibits failed";
        return zl::EResult_Failed;
    }

    LOG(INFO) << "set validator inhibits success";
    return zl::EResult_Success;
}

int32_t CITLValidatorImpl::ssp_cmd_poll()
{
    std::unique_lock<std::mutex> locker(mutex_);
    // LOG(INFO) << "validator polling...";
    if (impl_->hold_count > 0)
    {
        LOG(INFO) << "SSP_CMD_HOLD...";
        impl_->note_held = true;
        impl_->hold_count--;
        impl_->cmd.CommandData[0] = CCommands.SSP_CMD_HOLD;
        impl_->cmd.CommandDataLength = 1;
        if (!ssp_send_command())
        {
            LOG(ERROR) << "send hold command failed, send command failed";
            return zl::EResult_Failed;
        }

        return zl::EResult_Success;
    }

    // send poll command
    impl_->cmd.CommandData[0] = CCommands.SSP_CMD_POLL;
    impl_->cmd.CommandDataLength = 1;
    impl_->note_held = false;
    if (!ssp_send_command())
    {
        LOG(ERROR) << "send poll command failed";
        return zl::EResult_Failed;
    }

    // parse poll response
    int noteval = 0;
    for (uint8_t i = 1; i < impl_->cmd.ResponseDataLength; i++)
    {
        switch (impl_->cmd.ResponseData[i])
        {
        // this response indicates that the unit was reset and this is the first time
        // a poll has been called since the reset
        case CCommands.SSP_POLL_SLAVE_RESET:
            LOG(INFO) << "validator poll: Unit reset";
            break;
        case CCommands.SSP_POLL_READ_NOTE:
            if (impl_->cmd.ResponseData[i + 1] > 0)
            {
                noteval = get_channel_value(impl_->cmd.ResponseData[i + 1]);
                LOG(INFO) << "note in escrow, amount: " << noteval
                          << get_channel_currency(impl_->cmd.ResponseData[i + 1]);
                impl_->hold_count = impl_->hold_number;
            }
            else
            {
                LOG(INFO) << "reading note...";
            }
            i++;
            break;
        case CCommands.SSP_POLL_CREDIT_NOTE:
            noteval = get_channel_value(impl_->cmd.ResponseData[i + 1]);
            LOG(INFO) << "Credit " << noteval << get_channel_currency(impl_->cmd.ResponseData[i + 1]);
            impl_->stacked_notes++;
            // event pay
            if (obs_ != nullptr)
            {
                obs_->EventCredit(noteval, get_channel_currency(impl_->cmd.ResponseData[i + 1]));
            }
            i++;
            break;
        case CCommands.SSP_POLL_NOTE_REJECTING:
            LOG(WARNING) << "Rejecting note...";
            break;
        case CCommands.SSP_POLL_NOTE_REJECTED:
            LOG(WARNING) << "Note rejected";
            // QueryRejection();
            break;
        case CCommands.SSP_POLL_NOTE_STACKING:
            // LOG(INFO) << "Stacking note...";
            break;
        case CCommands.SSP_POLL_NOTE_STACKED:
            LOG(INFO) << "Note stacked";
            break;
        case CCommands.SSP_POLL_SAFE_NOTE_JAM:
            LOG(INFO) << "Safe jam";
            break;
        case CCommands.SSP_POLL_UNSAFE_NOTE_JAM:
            LOG(WARNING) << "Unsafe jam";
            break;
        case CCommands.SSP_POLL_DISABLED:
            break;
        case CCommands.SSP_POLL_FRAUD_ATTEMPT:
            LOG(INFO) << "Fraud attempt, note type: "
                      << get_channel_value(impl_->cmd.ResponseData[i + 1]);
            i++;
            break;
        case CCommands.SSP_POLL_STACKER_FULL:
            LOG(WARNING) << "Stacker full...";
            break;
        case CCommands.SSP_POLL_NOTE_CLEARED_FROM_FRONT:
            LOG(INFO) << "note cleared from front at reset, count: "
                      << get_channel_value(impl_->cmd.ResponseData[i + 1]);
            i++;
            break;
        case CCommands.SSP_POLL_NOTE_CLEARED_TO_CASHBOX:
            LOG(INFO) << "note cleared to stacker at reset, count: "
                      << get_channel_value(impl_->cmd.ResponseData[i + 1]);
            i++;
            break;
        case CCommands.SSP_POLL_CASHBOX_REMOVED:
            LOG(WARNING) << "Cashbox replaced";
            break;
        case CCommands.SSP_POLL_NOTE_PATH_OPEN:
            LOG(WARNING) << "Note path open";
            break;
        case CCommands.SSP_POLL_CHANNEL_DISABLE:
            LOG(WARNING) << "All channels inhibited, unit disabled";
            break;
        default:
            LOG(WARNING) << "Unrecognised poll response detected "
                         << impl_->cmd.ResponseData[i];
            break;
        }
    }
    return zl::EResult_Success;
}

int32_t CITLValidatorImpl::ssp_cmd_set_version()
{
    std::unique_lock<std::mutex> locker(mutex_);
    LOG(INFO) << "set validator version";

    impl_->cmd.CommandData[0] = CCommands.SSP_CMD_HOST_PROTOCOL_VERSION;
    impl_->cmd.CommandData[1] = 0x07;
    impl_->cmd.CommandDataLength = 2;
    if (!ssp_send_command() || !check_generic_responses())
    {
        LOG(ERROR) << "set validator version failed";
        return zl::EResult_Failed;
    }

    LOG(INFO) << "set validator version success";
    return zl::EResult_Success;
}

bool CITLValidatorImpl::check_generic_responses()
{
    LOG(INFO) << "check generic responses";

    if (impl_->cmd.ResponseData[0] == CCommands.SSP_RESPONSE_OK)
    {
        return true;
    }

    switch (impl_->cmd.ResponseData[0])
    {
    case CCommands.SSP_RESPONSE_COMMAND_CANNOT_BE_PROCESSED:
        if (impl_->cmd.ResponseData[1] == 0x03)
        {
            LOG(ERROR) << "Validator has responded with \"Busy\", command cannote be processed at this timer";
        }
        else
        {
            LOG(ERROR) << "Command response is CANNOT PROCESS COMMAND";
        }
        return false;
    case CCommands.SSP_RESPONSE_FAIL:
        LOG(ERROR) << "Command response is FAIL";
        return false;
    case CCommands.SSP_RESPONSE_KEY_NOT_SET:
        LOG(ERROR) << "Command response is KEY NOTE SET";
        return false;
    case CCommands.SSP_RESPONSE_PARAMETER_OUT_OF_RANGE:
        LOG(ERROR) << "Command response is PARAM OUT OF RANGE";
        return false;
    case CCommands.SSP_RESPONSE_SOFTWARE_ERROR:
        LOG(ERROR) << "Command response is SOFTWARE ERROR";
        return false;
    case CCommands.SSP_RESPONSE_COMMAND_NOT_KNOWN:
        LOG(ERROR) << "Command response is UNKNOWN";
        return false;
    case CCommands.SSP_RESPONSE_WRONG_NO_PARAMETERS:
        LOG(ERROR) << "Command response is WRONG PARAMETERS";
        return false;
    default: return false;
    }

    return false;
}

int CITLValidatorImpl::get_channel_value(int channel_num)
{
    if (channel_num >= 1 && channel_num <= impl_->number_of_channels)
    {
        for (const ChannelData& channel : impl_->unit_data_list)
        {
            if (channel.Channel == channel_num)
            {
                return channel.Value;
            }
        }
    }

    return -1;
}

std::string CITLValidatorImpl::get_channel_currency(int channel_num)
{
    if (channel_num >= 1 && channel_num <= impl_->number_of_channels)
    {
        for (const ChannelData& channel : impl_->unit_data_list)
        {
            if (channel.Channel == channel_num)
            {
                return std::string(channel.Currency);
            }
        }
    }

    return "";
}

bool CITLValidatorImpl::ssp_send_command()
{
    // LOG(INFO) << "validator send command";
    auto ret = pfunc_SSPSendCommand(&impl_->cmd, &impl_->info);
    if (ret == 0)
    {
        LOG(ERROR) << "send command failed, response status:" << (int)impl_->cmd.ResponseStatus << ret;
        return false;
    }
    return true;
}

void CITLValidatorImpl::poll_loop_worker(CITLValidatorImpl *ptr)
{
    if (ptr == nullptr)
    {
        return;
    }

    while (ptr->polling_thread_flag_)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
        if (ptr->polling_flag_)
        {
            ptr->ssp_cmd_poll();
        }
    }
}

#endif // Q_OS_WIN
